﻿using System;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using Apache.IoTDB;
using Apache.IoTDB.DataStructure;
using Iot.Shared.Enums;
using Iot.Shared.Helper;
using Iot.Shared.Path;
using Iot.TimingDatabase.EnumTypeCache;
using Iot.TimingDatabase.Models;
using Microsoft.Extensions.Logging;

namespace Iot.TimingDatabase.Helper;

public class IotDbConversion(IEnumTypeCache enumTypeCache, ILogger<IotDbConversion> logger)
{
    /// <summary>
    ///     枚举类型缓存数据
    /// </summary>
    private ConcurrentDictionary<Type, Lazy<FrozenDictionary<string, Enum>>> DicTypes { get; } = [];
    //TSDataType 
    //BOOLEAN = 0,
    //INT32 = 1,
    //INT64 = 2,
    //FLOAT = 3,
    //DOUBLE = 4,
    //TEXT = 5,
    //NONE = 7,
    //TIMESTAMP = 8,
    //DATE = 9,
    //BLOB = 10, // 0x0000000A
    //STRING = 11, // 0x0000000B


    /// <summary>
    ///     将设备模块数据转换为数据库可存储的格式
    /// </summary>
    /// <param name="keyPath">设备路径信息</param>
    /// <param name="name">数据名称</param>
    /// <param name="data">要转换的数据</param>
    /// <returns>返回一个元组，包含数据类型和转换后的值</returns>
    public (TSDataType Type, object? Value) DeviceModuleDataToDb(DevicePath keyPath, string name, object? data)
    {
        // 根据数据类型进行转换
        switch (data)
        {
            // 如果数据类型为long或uint，则转换为INT64类型
            case long or uint:
                return (TSDataType.INT64, Convert.ToInt64(data));
            // 如果数据类型为byte、ushort、short或int，则转换为INT32类型
            case byte or ushort or short or int:
                return (TSDataType.INT32, Convert.ToInt32(data));
            // 如果数据类型为double，则转换为DOUBLE类型
            case double:
                return (TSDataType.DOUBLE, data);
            // 如果数据类型为float，则转换为FLOAT类型
            case float:
                return (TSDataType.FLOAT, data);
            // 如果数据类型为Switching，则转换为BOOLEAN类型
            case Switching switching:
                return (TSDataType.BOOLEAN, switching == Switching.Alarm);
            // 如果数据类型为Enum，则将其类型缓存并转换为ToString类型
            case Enum enumValue:
                enumTypeCache.SetType(keyPath.Creation(name), enumValue);
                return (TSDataType.STRING, enumValue.ToString());
            // 如果数据类型未知，则记录错误日志并抛出异常
            default:
                logger.LogError("DeviceModuleDataToDb,未知数据类型，类型为：{Type},值为：{Data}", data?.GetType(), data);
                throw new SystemException($"DeviceModuleDataToDb,未知数据类型，类型为：{data?.GetType()},值为：{data}");
        }
    }


    /// <summary>
    ///     将对象转换为64位整数
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private static long ToInt64(object data)
    {
        // 使用Convert类将对象转换为64位整数
        return Convert.ToInt64(data);
    }


    /// <summary>
    ///     将对象转换为双精度浮点数
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private static double ToDouble(object data)
    {
        // 使用Convert类将对象转换为双精度浮点数
        return Convert.ToDouble(data);
    }


    /// <summary>
    ///     将传入的数据转换为Switching类型
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    /// <exception cref="SystemException"></exception>
    private Switching? ToSwitching(object data)
    {
        // 根据传入的数据类型进行判断
        switch (data)
        {
            // 如果传入的数据是bool类型
            case true:
                return Switching.Alarm;
            case false:
                return Switching.Normal;
            case null:
                return null;
            default: // 如果传入的数据不是bool类型
                // 记录错误日志
                logger.LogError("{functionName},未知数据,类型为：{Type},值为：{Data}", nameof(ToSwitching), data.GetType(), data);
                // 抛出异常
                throw new SystemException($"{nameof(ToSwitching)},未知数据，类型为：{data.GetType()},值为：{data}");
        }
    }

    private Enum? ToEnum(Type? enumType, object data)
    {
        if (enumType is not null && data is string value)
        {
            // 如果enumType不是枚举类型，则抛出异常
            if (!enumType.IsEnum)
            {
                logger.LogError("{functionName},未知枚举类型，枚举类型为：{enumType},数据类型为：{Type},数据值为：{Data}", nameof(ToEnum),
                    enumType, data.GetType(), data);
                throw new SystemException(
                    $"{nameof(ToEnum)},未知枚举类型，枚举类型为：{enumType},数据类型为：{data.GetType()},数据值为：{data}");
            }

            // 获取枚举类型的值，并转换为字典
            var enums = DicTypes.GetOrAdd(enumType,
                static a => new Lazy<FrozenDictionary<string, Enum>>(ValueFactory(a)));
            // 如果字典中存在对应的值，则返回该值
            if (enums.Value.TryGetValue(value, out var enumValue))
            {
                return enumValue;
            }
        }

        // 如果data不是int类型，则抛出异常
        logger.LogError("{functionName},未知数据，枚举类型为：{path},类型为：{Type},值为：{Data}", nameof(ToEnum), enumType,
            data.GetType(), data);
        return null;
    }

    /// <summary>
    ///     创建一个包含指定枚举类型所有值的不可变字典
    /// </summary>
    /// <param name="type">枚举类型</param>
    /// <returns>包含枚举值的不可变字典，键为枚举值的哈希码，值为枚举值本身</returns>
    private static FrozenDictionary<string, Enum> ValueFactory(Type type)
    {
        // 获取指定枚举类型的所有值，并转换为枚举类型
        // 然后创建一个不可变字典，其中键是枚举值的哈希码，值是枚举值本身
        return type.GetValues().ToFrozenDictionary(static b => b.ToString(), static b => b);
    }

    /// <summary>
    ///     获取数据集的列信息
    /// </summary>
    /// <param name="data"></param>
    /// <param name="basicsPath"></param>
    /// <returns></returns>
    /// <exception cref="SystemException"></exception>
    public ColumnInfo[] GetColumnInfo(SessionDataSet data, DevicePath? basicsPath)
    {
        // 获取数据集的列名，并将其转换为原始路径
        var names = data.ColumnNames.Select(ToOriginalPath).ToArray();
        // 获取数据集的列类型
        var types = data.GetColumnTypes();
        // 如果列名和列类型不一致，抛出异常
        if (names is null || names.Length != types.Length)
            throw new SystemException($"{nameof(GetColumnInfo)},数据名称与数据类型不一致,值为：{data}");
        // 创建列信息数组
        var result = new ColumnInfo[names.Length];
        // 遍历列名和列类型
        for (var i = 0; i < names.Length; i++)
        {
            Type? enumType = null;
            // 根据列类型获取对应的BusDataType
            var type = types[i] switch
            {
                TSDataType.BOOLEAN => BusDataType.Switching,
                TSDataType.STRING => GetIntegerOrEnum(names[i], out enumType),
                TSDataType.INT32 or TSDataType.INT64 => BusDataType.Integer,
                TSDataType.FLOAT or TSDataType.DOUBLE => BusDataType.Float,
                _ => BusDataType.None // 合并所有剩余情况
            };
            // 创建列信息
            result[i] = new ColumnInfo(names[i].Resolver(basicsPath), type, enumType);
        }

        return result;

        // 根据列名获取对应的BusDataType
        BusDataType GetIntegerOrEnum(string name, out Type? type)
        {
            enumTypeCache.TryGetType(name.Resolver(), out type);
            return BusDataType.Enum;
        }
    }


    private static string ToOriginalPath(string path)
    {
        // 如果路径 是 原始根路径，则直接返回路径
        if (path.IsOriginalRootPath()) return path;
        // 获取路径中根路径的起始位置
        var starts = path.IndexOf(DevicePathExtension.RootPath, StringComparison.Ordinal);
        // 返回路径中根路径之后的部分
        return path[starts..^1];
    }


    /// <summary>
    ///     最好在 Task 中调用   IotDb里面有异步转同步的代码 在主线程调用会卡死
    ///     .ConfigureAwait(false).GetAwaiter().GetResult()
    ///     this.Close().Wait();
    /// </summary>
    /// <param name="data"></param>
    /// <param name="basicsPath"></param>
    /// <returns></returns>
    public IotDbDataSet ToDataSet(SessionDataSet data, DevicePath? basicsPath = null)
    {
        // 获取列信息，根据传入的data和basicsPath
        var columnInfo = GetColumnInfo(data, basicsPath);


        // 创建一个二维数组，用于存储数据集中的数据
        // 数组的长度为列数，每个元素是一个长度为行数的对象数组

        var dataSet = Enumerable.Range(0, columnInfo.Length)
            .Select(static _ => new List<object?>())
            .ToArray();

        // 创建一个时间戳数组，用于存储每一行的时间戳
        var timestamps = new List<long>();
        while (data.HasNext())
        {
            // 获取当前行的数据
            var rowData = data.Next();
            // 将Unix时间戳转换为本地时间，并存储到时间戳列表中
            timestamps.Add(rowData.Timestamps);
            // 遍历每一列数据
            for (var colIdx = 0; colIdx < columnInfo.Length; colIdx++)
            {
                // 将当前单元格的值转换为指定类型，并存储到数据集中
                dataSet[colIdx].Add(ConvertValue(columnInfo[colIdx], rowData.Values[colIdx]));
            }
        }

        // 创建一个IotDbColumnData数组，用于存储每一列的数据
        var iotDbColumnData = columnInfo
            .Select((info, idx) => new IotDbColumnData(info, dataSet[idx].AsReadOnly()))
            .ToArray();
        // 返回一个新的IotDbDataSet对象，包含列数据和时间戳数组
        return new IotDbDataSet(iotDbColumnData.AsReadOnly(), timestamps.AsReadOnly());
    }

    private object? ConvertValue(ColumnInfo columnInfo, object? value)
    {
        // 根据value的值，返回相应的值
        return value switch
        {
            // 如果value是DBNull或null，则返回null
            DBNull or null => null,
            // 否则，根据columnInfo.Type的值，返回相应的值
            _ => columnInfo.Type switch
            {
                // 如果columnInfo.Type是None，则返回null
                BusDataType.None => null,
                // 如果columnInfo.Type是Integer，则调用ToInt64方法将value转换为Int64类型
                BusDataType.Integer => ToInt64(value),
                // 如果columnInfo.Type是Float，则调用ToDouble方法将value转换为Double类型
                BusDataType.Float => ToDouble(value),
                // 如果columnInfo.Type是Switching，则调用ToSwitching方法将value转换为Switching类型
                BusDataType.Switching => ToSwitching(value),
                // 如果columnInfo.Type是Enum，则调用ToEnum方法将value转换为Enum类型
                BusDataType.Enum => ToEnum(columnInfo.EnumType, value),
                // 如果columnInfo.Type不是上述类型，则抛出NotSupportedException异常
                _ => throw new NotSupportedException($"不支持的总线数据类型: {columnInfo.Type}")
            }
        };
    }
}